package com.mldong.modules.sys.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.mldong.auth.RolePermCodeCache;
import com.mldong.base.CommonPage;
import com.mldong.base.YesNoEnum;
import com.mldong.consts.CommonConstant;
import com.mldong.modules.sys.dto.MenuPageParam;
import com.mldong.modules.sys.dto.MenuParam;
import com.mldong.modules.sys.dto.SyncRouteParam;
import com.mldong.modules.sys.entity.Menu;
import com.mldong.modules.sys.entity.RoleMenu;
import com.mldong.modules.sys.enums.MenuAppCodeEnum;
import com.mldong.modules.sys.enums.MenuOpenTypeEnum;
import com.mldong.modules.sys.enums.MenuTypeEnum;
import com.mldong.modules.sys.enums.UserAdminTypeEnum;
import com.mldong.modules.sys.mapper.MenuMapper;
import com.mldong.modules.sys.mapper.RoleMenuMapper;
import com.mldong.modules.sys.service.DictService;
import com.mldong.modules.sys.service.MenuService;
import com.mldong.modules.sys.service.RbacService;
import com.mldong.modules.sys.vo.MenuVO;
import com.mldong.modules.sys.vo.RouteMenuVO;
import com.mldong.tree.TreeTool;
import com.mldong.web.LoginUserHolder;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 菜单 服务实现类
 * </p>
 *
 * @author mldong
 * @since 2023-09-21
 */
@Service
@RequiredArgsConstructor
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements MenuService {
    private final RoleMenuMapper roleMenuMapper;
    private final RolePermCodeCache rolePermCodeCache;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean save(MenuParam param) {
        param.setId(null);
        Menu menu = new Menu();
        BeanUtil.copyProperties(param, menu);
        if(StrUtil.isEmpty(param.getAppCode())){
            menu.setAppCode(LoginUserHolder.getAppCode());
        }
        menu.setVariable(JSONUtil.toJsonStr(param.getExt()));
        return super.save(menu);
    }

    @Override
    public boolean update(MenuParam param) {
        Menu menu = new Menu();
        BeanUtil.copyProperties(param, menu);
        menu.setVariable(JSONUtil.toJsonStr(param.getExt()));
        boolean success =  super.updateById(menu);
        if(success){
            for (String allKey : rolePermCodeCache.getAllKeys()) {
                rolePermCodeCache.remove(allKey);
            }
        }
        return success;
    }

    @Override
    public boolean removeBatchByIds(Collection<?> list) {
        boolean success = super.removeBatchByIds(list);
        if(success){
            for (String allKey : rolePermCodeCache.getAllKeys()) {
                rolePermCodeCache.remove(allKey);
            }
        }
        return success;
    }

    @Override
    public CommonPage<MenuVO> page(MenuPageParam param) {
        IPage<MenuVO> page = param.buildMpPage();
        QueryWrapper queryWrapper = param.buildQueryWrapper();
        queryWrapper.eq("t.is_deleted",YesNoEnum.NO);
        List<MenuVO> list = baseMapper.selectCustom(page, queryWrapper);
        page.setRecords(list);
        return CommonPage.toPage(page);
    }
    @Override
    public MenuVO findById(Long id) {
        return baseMapper.findById(id);
    }

    @Override
    public List<MenuVO> tree(MenuPageParam param) {
        QueryWrapper queryWrapper = param.buildQueryWrapper();
        queryWrapper.orderByAsc("sort");
        List<Menu> list = this.list(queryWrapper);
        List<MenuVO> voList = BeanUtil.copyToList(list,MenuVO.class);
        // 非超级管理员这里只返回所有菜单，非用户拥有的菜单设置disabled=true
        if(!LoginUserHolder.me().isSuperAdmin() && ObjectUtil.equals(param.getFilterByUser(),1)) {
            RbacService rbacService = SpringUtil.getBean(RbacService.class);
            List<String> menuIds = rbacService.getMenuIdsByUserId(LoginUserHolder.getUserId());
            voList.forEach(item -> {
                item.setDisabled(!menuIds.contains(item.getId().toString()));
            });
        }
        List<MenuVO> treeData = TreeTool.listToTree(BeanUtil.copyToList(voList,MenuVO.class),0L,MenuVO.class);
        return treeData;
    }

    /**
     * 同步前端路由
     * @param param
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean syncRoute(List<SyncRouteParam> param) {
        // 同步规则:
        // 1.默认只同步四级数据(可用递归，也可使用四层遍历)
        // 2.以appCode,code为唯一标识进行同步
        // 3.只同步isSync=true或isSync=1的数据
        // 4.同步时，有变化时，要更新，也可全字段更新
        // 5.ext扩展信息存放到variable
        // 6. 当前端删除路由时，后端对应的也要删除掉
        String appCode = LoginUserHolder.getAppCode();
        List<String> pidList = CollectionUtil.newArrayList("0");
        // 所有前端路由菜单id集合
        List<Long> menusIds = saveMenu(appCode,0L, pidList,param,1);
        LambdaQueryWrapper<Menu> delLambdaQueryWrapper = Wrappers.lambdaQuery();
        delLambdaQueryWrapper.eq(Menu::getAppCode,appCode)
                .notIn(Menu::getId,menusIds)
                .eq(Menu::getIsSync,1);
        // 删除不在前端路由同步的菜单
        List<Long> delIds = baseMapper.selectList(delLambdaQueryWrapper).stream().map(item->{
            return item.getId();
        }).collect(Collectors.toList());
        if(CollectionUtil.isNotEmpty(delIds)) {
            baseMapper.deleteBatchIds(delIds);
            // 删除角色菜单关系
            LambdaQueryWrapper<RoleMenu> delRoleMenuWrapper = Wrappers.lambdaQuery();
            delRoleMenuWrapper.in(RoleMenu::getMenuId,delIds);
            roleMenuMapper.delete(delRoleMenuWrapper);
        }
        rolePermCodeCache.remove(rolePermCodeCache.getAllKeys().toArray(new String[]{}));
        return true;
    }

    @Override
    public List<Dict> appList() {
        DictService dictService = SpringUtil.getBean(DictService.class);
        List<Dict> dictList = dictService.getByDictType("app_list");
        if(CollectionUtil.isNotEmpty(dictList)){
            return dictList;
        }
        Arrays.stream(MenuAppCodeEnum.values()).forEach(item->{
            Dict dict = new Dict();
            dict.put(CommonConstant.VALUE, item.toString().toLowerCase());
            dict.put(CommonConstant.LABEL, item.getMessage());
            dictList.add(dict);
        });
        return dictList;
    }

    @Override
    public List<RouteMenuVO> userRouteMenu(Long userId, String version) {
        List<Menu> menuList = baseMapper.selectList(
                Wrappers.lambdaQuery(Menu.class)
                        .eq(Menu::getEnabled,YesNoEnum.YES)
                        .eq(Menu::getAppCode,LoginUserHolder.getAppCode())
                        //.ne(Menu::getCode,"sys:routelist")
                        .in(Menu::getType,CollectionUtil.newArrayList(MenuTypeEnum.DIR,MenuTypeEnum.MENU))
                        .orderByAsc(Menu::getSort)
        );
        Map<String, List<Map<String, String>>> menuRelationMap = new HashMap<>();
        if(ObjectUtil.equals("art-design-pro", version)){
            menuRelationMap.putAll(buildMenuRelationMap());
        }
        if(!LoginUserHolder.me().isSuperAdmin()){
            RbacService rbacService = SpringUtil.getBean(RbacService.class);
            List<String> menuIds = rbacService.getMenuIdsByUserId(LoginUserHolder.getUserId());
            menuList = menuList.stream().filter(item->{
                return menuIds.contains(item.getId().toString());
            }).collect(Collectors.toList());
        }
        List<RouteMenuVO> list = new ArrayList<>();
        boolean superAdmin = LoginUserHolder.me().isSuperAdmin();
        menuList.forEach(menu -> {
            RouteMenuVO vo = new RouteMenuVO();
            vo.setId(menu.getId());
            vo.setParentId(menu.getParentId());
            vo.setName(menu.getCode());
            vo.setComponent(menu.getComponent());
            vo.setRedirect(null);
            vo.setPath(menu.getPath());
            vo.getMeta().set("order", Convert.toInt(menu.getSort()));
            vo.getMeta().set("title",menu.getName());
            vo.getMeta().putAll(JSONUtil.toBean(menu.getVariable(),Dict.class));
            if(StrUtil.isNotEmpty(menu.getIcon())){
                vo.getMeta().set("icon", menu.getIcon());
            }
            if(MenuOpenTypeEnum.LINK.getCode().equals(menu.getOpenType())){
                vo.getMeta().put("link",menu.getUrl());
            } else if(MenuOpenTypeEnum.IFRAME.getCode().equals(menu.getOpenType())){
                vo.getMeta().put("iframeSrc",menu.getUrl());
            }
            if(ObjectUtil.equals(YesNoEnum.NO.getCode(),menu.getIsShow())){
                if("v2".equalsIgnoreCase(version)){
                    vo.getMeta().put("hideMenu", true);
                } else {
                    vo.getMeta().put("hideInMenu", true);
                }
            }
            if(ObjectUtil.equals(YesNoEnum.YES.getCode(),menu.getIsCache())) {
                vo.getMeta().put("keepAlive", true);
            }
            String redirect = vo.getMeta().getStr("redirect");
            if(StrUtil.isNotEmpty(redirect)){
                vo.setRedirect(redirect);
            }
            if(ObjectUtil.equals("art-design-pro",version)){
                vo.getMeta().remove("link");
                vo.getMeta().remove("iframeSrc");
                if(MenuOpenTypeEnum.LINK.getCode().equals(menu.getOpenType())){
                    // 处理内部链接
                    vo.getMeta().put("link",menu.getUrl());
                    vo.getMeta().put("isIframe", false);
                } else if(MenuOpenTypeEnum.IFRAME.getCode().equals(menu.getOpenType())){
                    // 处理外部链接
                    vo.getMeta().put("link",menu.getUrl());
                    vo.getMeta().put("isIframe", true);
                }
                // 处理按钮权限
                if(MenuTypeEnum.MENU.getCode().equals(menu.getType())){
                    if(superAdmin){
                        Map<String,String> auth = new HashMap<>();
                        auth.put("title","超级管理员");
                        auth.put("authMark","admin");
                        vo.getMeta().put("authList",CollectionUtil.newArrayList(auth));
                    } else {
                        vo.getMeta().put("authList",menuRelationMap.get(menu.getCode()));
                    }
                }

            }
            list.add(vo);
        });
        return TreeTool.listToTree(list, 0L,RouteMenuVO.class);
    }
    /**
     * 根据应用编码和菜单编码查询
     * @param appCode
     * @param code
     * @return
     */
    private Menu findOneByAppCodeAndCode(String appCode,String code) {
        LambdaQueryWrapper<Menu> lambdaQueryWrapper = Wrappers.lambdaQuery();
        lambdaQueryWrapper.eq(Menu::getAppCode,appCode).eq(Menu::getCode,code);
        List<Menu> list = baseMapper.selectList(lambdaQueryWrapper);
        if(CollectionUtil.isNotEmpty(list)){
            return list.get(0);
        }
        return null;
    }

    /**
     * 保存菜单
     * @param appCode
     * @param parentId
     * @param pidList
     * @param list
     * @param currentLevel
     */
    private List<Long> saveMenu(String appCode,Long parentId,List<String> pidList,List<SyncRouteParam> list,int currentLevel) {
        List<Long> menusIds = new ArrayList<>();
        if(CollectionUtil.isEmpty(list) || currentLevel>8) return menusIds;
        list.stream().filter(item->{
            return ObjectUtil.equals(item.getIsSync(), YesNoEnum.Y) && !UserAdminTypeEnum.ADMIN.name().equalsIgnoreCase(item.getCode());
        }).forEach(item->{
            item.setAppCode(appCode);
            // 遍历第一级
            Menu menu = findOneByAppCodeAndCode(item.getAppCode(),item.getCode());
            if(menu==null) {
                // 新增
                menu = new Menu();
                menu.setParentId(parentId);
                BeanUtil.copyProperties(item,menu,"parentId","children");
                menu.setVariable(JSONUtil.toJsonStr(item.getExt()));
                menu.setPids(StrUtil.join(",",pidList));
                baseMapper.insert(menu);
            } else {
                if(ObjectUtil.equals(menu.getIsSync(),YesNoEnum.Y)){
                    // 更新
                    BeanUtil.copyProperties(item,menu,"id","parentId","children");
                    menu.setVariable(JSONUtil.toJsonStr(item.getExt()));
                    menu.setPids(StrUtil.join(",",pidList));
                    baseMapper.updateById(menu);
                }
            }
            List<String> newPidList = new ArrayList<>();
            newPidList.addAll(pidList);
            newPidList.add(menu.getId().toString());
            menusIds.add(menu.getId());
            List<Long> childIds = saveMenu(appCode,menu.getId(),newPidList,item.getChildren(),currentLevel+1);
            menusIds.addAll(childIds);
        });
        return menusIds;
    }
    /**
     * 构建菜单、目录及其下按钮、接口的关系
     * @return Map<String, List<Map<String,String>>> key为菜单code，value为该菜单下的按钮和接口列表
     */
    private Map<String, List<Map<String,String>>> buildMenuRelationMap() {
        // 查询所有启用的菜单，按类型分类
        List<Menu> allMenus = baseMapper.selectList(
                Wrappers.lambdaQuery(Menu.class)
                        .eq(Menu::getEnabled, YesNoEnum.YES)
                        .eq(Menu::getAppCode, LoginUserHolder.getAppCode())
        );

        // 按菜单code分组，便于快速查找
        Map<String, Menu> menuMap = allMenus.stream()
                .filter(menu -> menu.getCode() != null)
                .collect(Collectors.toMap(Menu::getCode, menu -> menu));

        // 构建关系Map
        Map<String, List<Map<String,String>>> relationMap = new HashMap<>();
        // 当前用户拥有的权限标识
        List<String> permCodeList =  StpUtil.getPermissionList();
        // 遍历所有菜单，为每个菜单或目录构建其子项（按钮、接口等）
        for (Menu menu : allMenus) {
            // 只处理目录和菜单类型，作为关系映射的key
            if (MenuTypeEnum.DIR.getCode().equals(menu.getType()) || MenuTypeEnum.MENU.getCode().equals(menu.getType())) {
                List<Map<String,String>> children = new ArrayList<>();

                // 查找该菜单下的所有子菜单（包括按钮和接口）
                findChildren(menu, allMenus, permCodeList, children);

                relationMap.put(menu.getCode(), children);
            }
        }

        return relationMap;
    }

    /**
     * 递归查找菜单的所有子菜单（按钮和接口）
     * @param parent 父级菜单
     * @param allMenus 所有菜单列表
     * @param permCodeList 权限标识列表
     * @param result 结果列表
     */
    private void findChildren(Menu parent, List<Menu> allMenus, List<String> permCodeList, List<Map<String,String>> result) {
        for (Menu menu : allMenus) {
            // 如果菜单的父ID等于当前父菜单的ID，则为子菜单
            if (parent.getId().equals(menu.getParentId())) {
                Map<String,String> child = new HashMap<>();
                child.put("authMark", menu.getCode());
                child.put("title", menu.getName());
                if(permCodeList.contains(menu.getCode())){
                    result.add(child);
                }
                // 继续递归查找更深层的子菜单
                findChildren(menu, allMenus, permCodeList, result);
            }
        }
    }

}
